/*
 *   Copyright (c) 2011 Hitachi Data Systems, Inc.
 *
 *   Permission is hereby granted to  this software and associated
 *   documentation files (the "Software"), subject to the terms and
 *   conditions of the Sample Source Code License (SSCL) delivered
 *   with this Software. If you do not agree to the terms and
 *   conditions of the SSCL,
 *
 *     (i)  you must close this file and delete all copies of the
 *          Software, and
 *     (ii) any permission to use the Software is expressly denied.
 *
 */

package com.hds.hcp.apihelpers.query.result;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.List;

import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.codehaus.jackson.map.MappingJsonFactory;

/**
 * This class defines the main container for parsing the <code>queryResult</code> response
 * returned from a query request over HTTP. This implementation processes the response in the
 * JSON format.
 * <p>
 * The <code>queryResult</code> consists of 3 main sections for which get/set methods are
 * provided to obtain those objects:
 * <table border="1" cellpadding="4">
 * <tr><td>query</td><td>indicates the start and end times for the query</td></tr>
 * <tr><td>resultSet</td><td>list of detailed records of this result. It may consist of only part of the query result.</td></tr>
 * <tr><td>status</td><td>indicates the information about the current <code>resultSet</code>. It contains the overall batch status
 * code, number of records in the <code>resultSet</code>, and possible error message.</td></tr>
 * </table>
 * <p>
 * For details of the <code>queryResult</code>, refer to the HCP user documentation.
 * 
 * @version 5.1
 */
public class QueryResult {

	private Query _query;
	private List<ResultSetRecord> _resultSet;
	private ResultSet _resultSetParser;
	private Status _status;
	private Facets _facetsParser;
	
	public QueryResult() {};
	
	/**
	 * Static function that constructs a <code>QueryResult</code> class from a Reader
	 * object containing JSON formatted <code>queryResult</code> structure as defined in the HCP user documentation.
	 * 
	 * @param inReader The <code>java.io.Reader</code> object containing the JSON formatted response.
	 * @return QueryResult
	 * @throws Exception
	 */
	static public QueryResult parse(Reader inReader) throws Exception {
		QueryResult RetVal = null;
		
		// Setup the JSON parser feeding in the response.
		JsonParser jp = (new MappingJsonFactory()).createJsonParser(inReader);
		
		// Make sure we are at top level.
		if (jp.nextToken() == JsonToken.START_OBJECT) {
		
			// Eat the token, and make sure we are at queryResult level.
			if (jp.nextToken() == JsonToken.FIELD_NAME && "queryResult".equals(jp.getCurrentName())) {

				// Eat the query result token and make top level object token for queryResult.
				if (jp.nextToken() == JsonToken.START_OBJECT) {
					
					// Parse the queryResult into the object.
					RetVal = jp.readValueAs(QueryResult.class);
				}
				else
					throw new IOException("Unexpected JSON token: After queryResult field name.");
			}
			else
				throw new IOException("Unexpected JSON token: queryResult field not found");
		}
		else
			throw new IOException("Unexpected JSON token: Not Start of Object");
		return RetVal;
	}
	
	/**
	 * Static function that constructs a <code>QueryResult</code> class from an InputStream
	 * object containing JSON formatted <code>queryResult</code> structure as defined in the
	 * HCP user documentation.
	 * 
	 * @param inStream An InputStream object containing the JSON formatted result to parse.
	 * @return QueryResult 
	 * @throws Exception
	 */
	static public QueryResult parse(InputStream inStream) throws Exception {
		QueryResult RetVal = null;
		
		// Setup the JSON parser feeding in the response.
		JsonParser jp = (new MappingJsonFactory()).createJsonParser(inStream);
		
		// Make sure we are at top level.
		if (jp.nextToken() == JsonToken.START_OBJECT) {
		
			// Eat the token, and make sure we are at queryResult level.
			if (jp.nextToken() == JsonToken.FIELD_NAME && "queryResult".equals(jp.getCurrentName())) {

				// Eat the query result token and make top level object token for queryResult.
				if (jp.nextToken() == JsonToken.START_OBJECT) {
					
					// Parse the queryResult into the object.
					RetVal = jp.readValueAs(QueryResult.class);
				}
				else
					throw new IOException("Unexpected JSON token: After queryResult field name.");
			}
			else
				throw new IOException("Unexpected JSON token: queryResult field not found");
		}
		else
			throw new IOException("Unexpected JSON token: Not Start of Object");

		return RetVal;
	}
	
	/**
	 * This method provides a mechanism to parse an InputStream on a previously constructed 
	 * object.
	 * <p>
	 * It will also allow for displaying the parsed content out to <code>System.out</code>
	 * for debugging purposes. The format will be in a more human readable form.
	 *  
	 * @param inStream The <code>InputStream</code> to parse and set the object with.
	 * @param inDebug The <code>Boolean</code> value indicating whether the parsed content 
	 *     should be displayed to <code>System.out</code> for debugging purposes.
	 * @throws Exception
	 */
	public void parseAndPrint(InputStream inStream, Boolean inDebug) throws Exception {
		try {
			JsonParser jp = (new MappingJsonFactory()).createJsonParser(inStream);
			
			// Make sure we are at top level.
			if (jp.nextToken() == JsonToken.START_OBJECT) {

				// Make sure we are at queryResult level.
				if (jp.nextToken() == JsonToken.FIELD_NAME && "queryResult".equals(jp.getCurrentName())) {
	
					// Eat the top level object token for queryResult.
					if (jp.nextToken() == JsonToken.START_OBJECT) {

						// Make sure we are at query level.
						if (jp.nextToken() == JsonToken.FIELD_NAME && "query".equals(jp.getCurrentName())) {
							jp.nextToken(); // Eat this token to prepare class load.
						
							// Read in query object.
							_query = jp.readValueAs(Query.class);

							if (inDebug)
								System.out.println("Query:\n    Start: " + _query.getStart() + "\n    End:   " + _query.getEnd());
						}
					}
						
					// See if next token indicates there is a resultSet to parse.
					_resultSetParser = new ResultSet();
					if (JsonToken.FIELD_NAME == jp.nextToken() && "resultSet".equals(jp.getCurrentName())) {
	
						// Point the resultSet parser to the JsonParser
						_resultSetParser.setParser(jp);

						while ( ! _resultSetParser.isLast() ) {
							ResultSetRecord currentRecord;
							
							currentRecord = _resultSetParser.getNext();
							
							if (inDebug) {
								System.out.println("Record: (" + (currentRecord.isVerbose() ? "verbose" : "basic") + ")" 
									    + "\n    Version:    " + currentRecord.getVersion() 
										+ "\n    UrlName:    " + currentRecord.getUrlName()
										+ "\n    Operation:  " + currentRecord.getOperation()
										+ "\n    ChangeTime: " + currentRecord.getChangeTimeMilliseconds());
								if (currentRecord.isVerbose()) {
									System.out.println("    CustomMetadata: " + currentRecord.getCustomMetadata());
								}
							}
						}
					}
					
					// Should be at the status object.  So parse it.
					if (jp.nextToken() == JsonToken.FIELD_NAME && "status".equals(jp.getCurrentName())) {
						jp.nextToken(); // Eat this token to prepare class load.
						
						_status = jp.readValueAs(Status.class);
						
						if (inDebug)
							System.out.println("Status:\n    Message: " + _status.getMessage() 
									+ "\n    Code:    " + _status.getCode() 
									+ "\n    Results: " + _status.getResults());
						
					}
					
					// See if next token indicates there is a resultSet to parse.
					_facetsParser = new Facets();
					if (JsonToken.FIELD_NAME == jp.nextToken() && "facets".equals(jp.getCurrentName())) {
	
						// Point the resultSet parser to the JsonParser
						_facetsParser.setParser(jp);

						while ( ! _facetsParser.isLast() ) {
							FacetRecord currentRecord;
							
							currentRecord = _facetsParser.getNext();
							
							if (inDebug) {
								System.out.println("FacetRecord: " 
									    + "\n    Property:    " + currentRecord.getProperty());
							}
						}
					}
					

				}
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			
			throw e;
		}
	}

	/**
	 * Getter method for the <code>Query</code> object in this object.
	 * @return Query object
	 */
	public Query getQuery() {
		return _query;
	}
	
	/**
	 * Setter method for the <code>Query</code> object in this object.
	 * @param inQuery The <code>Query</code> object for input.
	 */
	public void setQuery(Query inQuery) {
		_query = inQuery;
	}
	
	/**
	 * Getter method for the collection of <code>ResultSetRecord</code> objects for
	 * the <code>ResultSet</code>.
	 * @return List ordered collection of <code>ResultSetRecord</code>.
	 */
	public List<ResultSetRecord> getResultSet() {
		return _resultSet;
	}
	
	/**
	 * Setter method for the List collection of <code>ResultSetRecords</code> to set
	 * in the object.
	 * @param inResultSet List ordered collection of <code>ResultSetRecord</code>
	 *        to set in object.
	 */
	public void setResultSet(List<ResultSetRecord> inResultSet) {
		_resultSet = inResultSet;
	}
	
	/**
	 * Getter method for the <code>Status</code> for this object.
	 * @return <code>Status</code> object.
	 */
	public Status getStatus() {
		return _status;
	}
	
	/**
	 * Setter method for the <code>Status</code> object into the object.
	 * @param inStatus The <code>Status</code> object to set in object.
	 */
	public void setStatus(Status inStatus) {
		_status = inStatus;
	}
}
